home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994…tember: Reference Library / Dev.CD Sep 94.toast / Periodicals / develop / develop Issue 6 / develop 6 code / TCP / NewsWatcher / NewsWatcher 2.0d15 source / source / subject.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-08-30  |  21.0 KB  |  735 lines  |  [TEXT/KAHL]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     subject.c
  4.  
  5.     This module handles the creation of subject windows.
  6.     
  7.     Portions copyright © 1990, Apple Computer.
  8.     Portions copyright © 1993, Northwestern University.
  9.  
  10. ----------------------------------------------------------------------------*/
  11.  
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <stdio.h>
  15.  
  16. #include "glob.h"
  17. #include "child.h"
  18. #include "header.h"
  19. #include "kill.h"
  20. #include "mark.h"
  21. #include "nntp.h"
  22. #include "open.h"
  23. #include "qsort.h"
  24. #include "resize.h"
  25. #include "subject.h"
  26. #include "util.h"
  27.  
  28.  
  29. static Handle gSubjectStrings;     /* handle to subject strings for sort comparison function */
  30.  
  31.  
  32.  
  33. /*----------------------------------------------------------------------------
  34.     SubjectCompare 
  35.     
  36.     Compares two subject strings, ignoring any "Re:" prefixes.
  37.     
  38.     Entry:    aPtr = pointer to first subject string.
  39.             bPtr = pointer to second subject string.
  40.             
  41.     Exit:    function result:
  42.                 < 0 if first string < second string.
  43.                 = 0 if first string == second string.
  44.                 > 0 if first string > second string.
  45. ----------------------------------------------------------------------------*/
  46.  
  47. static short SubjectCompare (char *aPtr, char *bPtr)
  48. {    
  49.     /* Skip spaces. */
  50.     
  51.     while (*aPtr == ' ') aPtr++;
  52.     while (*bPtr == ' ') bPtr++;
  53.         
  54.     /* Remove reply indicators. */
  55.  
  56.     if (strncasecmp(aPtr, "Re:", 3) == 0) aPtr += 3;
  57.     if (strncasecmp(bPtr, "Re:", 3) == 0) bPtr += 3;
  58.     
  59.     /* Skip spaces. */
  60.     
  61.     while (*aPtr == ' ') aPtr++;
  62.     while (*bPtr == ' ') bPtr++;
  63.     
  64.     /* Compare strings. */
  65.     
  66.     return strcasecmp(aPtr,bPtr);
  67. }
  68.  
  69.  
  70.  
  71. /*----------------------------------------------------------------------------
  72.     GetSubjects
  73.     
  74.     Gets a range of subject headers from the news server and stores them in
  75.     a subject array.
  76.     
  77.     Entry:    newsGroup = name of the group.
  78.             first = first article number to get.
  79.             last = last article number to get.
  80.             subjectArray = handle to subject array.
  81.             index = index in subject array to store first subject.
  82.             strings = handle to strings block.
  83.             *nextStringOffset = offset of next available location in
  84.                 strings block.
  85.     
  86.     Exit:    function result = true if no error, false if error.
  87.             *numFetched = number of subjects fetched.
  88.             *nextStringOffset updated.
  89.     
  90.     The caller must preallocate memory in the subject array for at least
  91.     (last-first+1) new subjects.
  92.     
  93.     If the group does not exist the function result is true and 
  94.     *numFetched=0.
  95. ----------------------------------------------------------------------------*/
  96.  
  97. static Boolean GetSubjects (char *newsGroup, long first, long last, 
  98.     TSubject **subjectArray, short index,
  99.     Handle strings, long *nextStringOffset,
  100.     short *numFetched)
  101. {
  102.     short result;
  103.     THeader **headers = nil; 
  104.     short numHeaders;
  105.     TSubject theSubject;
  106.     THeader *pHeader, *pHeaderEnd;
  107.     TSubject *pSubject;
  108.  
  109.     result = GetHeaders(newsGroup, "SUBJECT", first, last, strings, nextStringOffset,
  110.         nil, 255, &headers, &numHeaders);
  111.     if (result == 2) return false;
  112.     if (result == 1) {
  113.         *numFetched = 0;
  114.         return true;
  115.     }
  116.     
  117.     pHeaderEnd = *headers + numHeaders;
  118.     pSubject = *subjectArray + index;
  119.     for (pHeader = *headers; pHeader < pHeaderEnd; pHeader++, pSubject++, index++) {
  120.         theSubject.subjectOffset = pHeader->offset;
  121.         theSubject.authorOffset = -1;
  122.         theSubject.number = pHeader->number;
  123.         theSubject.myIndex = index;
  124.         theSubject.collapsed = gPrefs.showThreadsCollapsed;
  125.         theSubject.read = false;
  126.         theSubject.drawTriangleFilled = false;
  127.         theSubject.onlyRedrawTriangle = false;
  128.         theSubject.onlyRedrawCheck = false;
  129.         theSubject.highlight = 0;
  130.         *pSubject = theSubject;
  131.     }
  132.  
  133.     MyDisposHandle((Handle)headers);
  134.     *numFetched = numHeaders;
  135.     return true;
  136. }
  137.  
  138.  
  139.  
  140. /*----------------------------------------------------------------------------
  141.     GetAuthors
  142.     
  143.     Gets a range of author headers from the news server and stores them in
  144.     a subject array.
  145.     
  146.     Entry:    newsGroup = name of the group.
  147.             first = first article number to get.
  148.             last = last article number to get.
  149.             subjectArray = handle to subject array.
  150.             index = starting index in subject array.
  151.             num = number of subject records.
  152.             strings = handle to strings block.
  153.             *nextStringOffset = offset of next available location in
  154.                 strings block.
  155.     
  156.     Exit:    function result = true if no error, false if error.
  157.             *nextStringOffset updated.
  158.     
  159.     GetSubjects must be called before GetAuthors to initialize the
  160.     subject array elements.
  161. ----------------------------------------------------------------------------*/
  162.  
  163. static Boolean GetAuthors (char *newsGroup, long first, long last, 
  164.     TSubject **subjectArray, short index, short num,
  165.     Handle strings, long *nextStringOffset)
  166. {
  167.     short result;
  168.     THeader **headers = nil; 
  169.     short numHeaders;
  170.     THeader *pHeader, *pHeaderEnd;
  171.     TSubject *pSubject, *pSubjectEnd;
  172.  
  173.     result = GetHeaders(newsGroup, "From", first, last, strings, nextStringOffset,
  174.         PrettifyName, 255, &headers, &numHeaders);
  175.     if (result == 2) return false;
  176.     if (result == 1) return true;
  177.     
  178.     pHeader = *headers;
  179.     pHeaderEnd = *headers + numHeaders;
  180.     pSubject = *subjectArray + index;
  181.     pSubjectEnd = pSubject + num;
  182.     while (pHeader < pHeaderEnd && pSubject < pSubjectEnd) {
  183.         if (pHeader->number < pSubject->number) {
  184.             pHeader++;
  185.         } else if (pHeader->number > pSubject->number) {
  186.             pSubject++;
  187.         } else {
  188.             pSubject->authorOffset = pHeader->offset;
  189.             pHeader++;
  190.             pSubject++;
  191.         }
  192.     }
  193.     
  194.     MyDisposHandle((Handle)headers);
  195.     
  196.     return true;
  197. }
  198.  
  199.  
  200.  
  201. /*----------------------------------------------------------------------------
  202.     GetSubjectsAndAuthorsFromNet gets unread subjects and authors 
  203.     for a group from the NNTP server.
  204.     
  205.     Entry:    groupName = the group name.
  206.             theGroup = pointer to the group record.
  207.             kind = kind of group (kFullGroup, kNewGroup, or kUserGroup).
  208.             
  209.     Exit:    function result = true if subjects fetched, false if error.
  210.             *subjectArray = handle to subject array, with the fields
  211.                 subjectOffset, authorOffset, number, read, and myIndex initialized.
  212.             *numSubjects = number of subjects fetched.
  213.             *subjectStrings = handle to subject strings.
  214.             *firstFetched = article number of first article fetched.
  215.             
  216.     If the group does not exist, the function result is true and 
  217.     *numSubjects = 0. In other words, a deleted group is treated the
  218.     same as a group with no unread articles.
  219. ----------------------------------------------------------------------------*/
  220.  
  221. static Boolean GetSubjectsAndAuthorsFromNet (char *groupName, TGroup *theGroup, 
  222.     EWindowKind kind, TSubject ***subjectArray, short *numSubjects, 
  223.     Handle *subjectStrings, long *firstFetched)
  224. {
  225.     TSubject **array = nil;
  226.     short index = 0;
  227.     Handle strings = nil;
  228.     OSErr err;
  229.     long nextStringOffset = 0;
  230.     TUnread **unread;
  231.     long first, last, numToFetch, totNumToFetch;
  232.     short numAllocated;
  233.     short numFetched;
  234.     
  235.     array = (TSubject**)MyNewHandle(100*sizeof(TSubject));
  236.     numAllocated = 100;
  237.     strings = MyNewHandle(20000);
  238.     
  239.     if (kind == kUserGroup) {
  240.         unread = theGroup->unread;
  241.         totNumToFetch = theGroup->numUnread;
  242.         first = (**unread).firstUnread;
  243.         last = (**unread).lastUnread;
  244.         if (totNumToFetch > gPrefs.maxFetch) {
  245.             while (unread != nil) {
  246.                 totNumToFetch -= last - first + 1;
  247.                 if (totNumToFetch <= gPrefs.maxFetch) {
  248.                     first = last - (gPrefs.maxFetch - totNumToFetch) + 1;
  249.                     break;
  250.                 }
  251.                 unread = (**unread).next;
  252.                 if (unread != nil) {
  253.                     first = (**unread).firstUnread;
  254.                     last = (**unread).lastUnread;
  255.                 }
  256.             }
  257.         }
  258.         *firstFetched = first;
  259.         while (unread != nil) {
  260.             numToFetch = last - first + 1;
  261.             if (index + numToFetch > numAllocated) {
  262.                 numAllocated = index + numToFetch + 100;
  263.                 MySetHandleSize((Handle)array, numAllocated*sizeof(TSubject));
  264.             }
  265.             if (!GetSubjects(groupName, first, last, array, index, strings,
  266.                 &nextStringOffset, &numFetched)) goto exit;
  267.             if (gPrefs.showAuthors)
  268.                 if (!GetAuthors(groupName, first, last, array, index, numFetched, strings,
  269.                     &nextStringOffset)) goto exit;
  270.             index += numFetched;
  271.             unread = (**unread).next;
  272.             if (unread != nil) {
  273.                 first = (**unread).firstUnread;
  274.                 last = (**unread).lastUnread;
  275.             }
  276.         }
  277.     } else {
  278.         first = theGroup->firstMess;
  279.         last = theGroup->lastMess;
  280.         numToFetch = last - first + 1;
  281.         if (numToFetch > gPrefs.maxFetch) {
  282.             numToFetch = gPrefs.maxFetch;
  283.             first = last - numToFetch + 1;
  284.         }
  285.         *firstFetched = first;
  286.         if (numToFetch > numAllocated) {
  287.             numAllocated = numToFetch;
  288.             MySetHandleSize((Handle)array, numAllocated*sizeof(TSubject));
  289.         }
  290.         if (!GetSubjects(groupName, first, last, array, index, strings,
  291.             &nextStringOffset, &numFetched)) goto exit;
  292.         if (gPrefs.showAuthors)
  293.             if (!GetAuthors(groupName, first, last, array, index, numFetched, strings,
  294.                 &nextStringOffset)) goto exit;
  295.         index = numFetched;
  296.     }
  297.     
  298.     MySetHandleSize((Handle)array, index*sizeof(TSubject));
  299.     MySetHandleSize(strings, nextStringOffset);
  300.  
  301.     *subjectArray = array;
  302.     *numSubjects = index;
  303.     *subjectStrings = strings;
  304.     return true;
  305.  
  306. exit:
  307.  
  308.     MyDisposHandle((Handle)array);
  309.     MyDisposHandle(strings);
  310.     return false;
  311. }
  312.  
  313.  
  314.  
  315. /*----------------------------------------------------------------------------
  316.     SortSubjectArrayCompare1 is the comparison function used to sort
  317.     an array of pointers to subject records into increasing order by
  318.     subject (ignoring any "Re:" prefixes).
  319.     
  320.     Entry:    p = pointer to pointer to TSubject record.
  321.             q = pointer to pointer to TSubject record.
  322.             gSubjectStrings = locked handle to subject strings.
  323.             
  324.     Exit:    function result:
  325.                 < 0 if first subject < second subject.
  326.                 = 0 if first subject == second subject.
  327.                 > 0 if first subject > second subject.
  328.                 
  329.     Article numbers are used as a secondary sort key. 
  330. ----------------------------------------------------------------------------*/
  331.  
  332. static short SortSubjectArrayCompare1 (TSubject **p, TSubject **q)
  333. {
  334.     short result;
  335.     static short Counter = 0;
  336.  
  337.     if ((++Counter & 0x1f) == 0) {
  338.         GiveTime();
  339.         Counter = 0;
  340.     }
  341.     
  342.     result = SubjectCompare(*gSubjectStrings + (**p).subjectOffset, 
  343.         *gSubjectStrings + (**q).subjectOffset);
  344.     if (result != 0 ) return result;
  345.     return (**p).number < (**q).number ? -1 : +1;
  346. }
  347.  
  348.  
  349.  
  350. /*----------------------------------------------------------------------------
  351.     SortSubjectArrayCompare2 is the comparison function used to sort
  352.     an array of pointers to subject records into thread order.
  353.     
  354.     Entry:    p = pointer to pointer to TSubject record.
  355.             q = pointer to pointer to TSubject record.
  356.             
  357.     Exit:    function result:
  358.                 < 0 if first subject < second subject.
  359.                 = 0 if first subject == second subject.
  360.                 > 0 if first subject > second subject.
  361. ----------------------------------------------------------------------------*/
  362.  
  363. static short SortSubjectArrayCompare2 (TSubject **p, TSubject **q)
  364. {
  365.     static short Counter = 0;
  366.  
  367.     if ((++Counter & 0x1f) == 0) {
  368.         GiveTime();
  369.         Counter = 0;
  370.     }
  371.     
  372.     if ((**p).threadHeadNumber == (**q).threadHeadNumber)
  373.         return (**p).number < (**q).number ? -1 : +1;
  374.     return (**p).threadHeadNumber < (**q).threadHeadNumber ? -1 : +1;
  375. }
  376.  
  377.  
  378.  
  379. /*----------------------------------------------------------------------------
  380.     SortSubjectArray sorts a subject array into thread order. 
  381.  
  382.     The array itself is not sorted into thread order. Rather, an array of 
  383.     pointers to the subject records is sorted into thread order. This array
  384.     of pointers is then used to build the List Manager list for the subject
  385.     window, at which point the array is discarded. There are two reasons 
  386.     for doing this: 
  387.     
  388.     1. The subject array must be in increasing order by article number. 
  389.        This property is required by the function UpdateUnreadList in mark.c.
  390.        
  391.     2. Sorting 4 byte pointers to the TSubject records is faster than
  392.        sorting the records themselves.
  393.     
  394.     Entry:    subjectArray = handle to locked subject array.
  395.             subjectArrayPointers = preallocated nonrelocatable block
  396.                 of size 4 * numSubjects.
  397.             numSubjects = number of subjects.
  398.             subjectStrings = handle to subject strings.
  399.             
  400.     Exit:    function result = true if no error, false if error. 
  401.             subjectArrayPointers = array of pointers to elements of
  402.                 subjectArray, sorted into thread order.
  403.             fields threadOrdinal, threadLength, nextInThread, and
  404.                 threadHeadIndex initialized in subjectArray.
  405. ----------------------------------------------------------------------------*/
  406.  
  407. static Boolean SortSubjectArray (TSubject **subjectArray, 
  408.     TSubject **subjectArrayPointers, short numSubjects, 
  409.     Handle subjectStrings)
  410. {
  411.     TSubject *pSubject;
  412.     TSubject **pPointer, **qPointer, **threadHeadPointer=nil;
  413.     char *threadHeadSubject=0;
  414.     long threadHeadNumber;
  415.     Boolean newThread;
  416.     short threadOrdinal=0;
  417.     short i;
  418.     short threadHeadIndex;
  419.     Boolean result;
  420.             
  421.     /*    Initialize the pointer array. */
  422.     
  423.     pPointer = subjectArrayPointers;
  424.     pSubject = *subjectArray;
  425.     for (i = 0; i < numSubjects; i++) {
  426.         *pPointer = pSubject;
  427.         pPointer++;
  428.         pSubject++;
  429.     }
  430.     
  431.     /*    Sort the pointer array into increasing order by subject (ignoring
  432.         any "Re:" prefixes). This brings threads together, although the
  433.         threads are not in the right order yet. The article numbers are
  434.         used as a secondary sort key to kep the articles within a thread
  435.         in the correct order. */
  436.         
  437.     gSubjectStrings = subjectStrings;
  438.     HLock(gSubjectStrings);
  439.     result = FastQSort(subjectArrayPointers, numSubjects, sizeof(Ptr),
  440.         (short(*)(void*,void*))SortSubjectArrayCompare1);
  441.     HUnlock(gSubjectStrings);
  442.     if (!result) return false;
  443.     
  444.     /*    Set the following fields in each element of the subject array:
  445.     
  446.         threadOrdinal = index of article in thread (1,2,3,...).
  447.         threadLength = total number of articles in thread.
  448.         nextInThread = index in subject array of next article in thread,
  449.             or -1 if none.
  450.         threadHeadIndex = index in subject array of first article in
  451.             thread.
  452.         threadHeadNumber = article number of first article in thread. */
  453.     
  454.     pPointer = subjectArrayPointers;
  455.     for (i = 0; i < numSubjects; i++) {
  456.         newThread = i == 0 ? true :
  457.             SubjectCompare(*subjectStrings + (**pPointer).subjectOffset,
  458.                 threadHeadSubject) != 0;
  459.         if (newThread) {
  460.             if (i != 0) {
  461.                 for (qPointer = threadHeadPointer; qPointer < pPointer;
  462.                     qPointer++)
  463.                 {
  464.                     (**qPointer).threadLength = threadOrdinal;
  465.                     (**qPointer).nextInThread = (qPointer < pPointer-1) ? 
  466.                         (**(qPointer+1)).myIndex : -1;
  467.                 }
  468.             }
  469.             threadHeadPointer = pPointer;
  470.             threadHeadNumber = (**pPointer).number;
  471.             threadHeadSubject = *subjectStrings + (**pPointer).subjectOffset;
  472.             threadOrdinal = 0;
  473.             threadHeadIndex = (**pPointer).myIndex;
  474.         }
  475.         threadOrdinal++;
  476.         (**pPointer).threadOrdinal = threadOrdinal;
  477.         (**pPointer).threadHeadNumber = threadHeadNumber;
  478.         (**pPointer).threadHeadIndex = threadHeadIndex;
  479.         pPointer++;
  480.     }
  481.     for (qPointer = threadHeadPointer; qPointer < pPointer; qPointer++) {
  482.         (**qPointer).threadLength = threadOrdinal;
  483.         (**qPointer).nextInThread = (qPointer < pPointer-1) ? 
  484.             (**(qPointer+1)).myIndex : -1;
  485.     }
  486.     
  487.     /*    Sort the pointer array into final thread order. The primary
  488.         sort key is threadHeadNumber. The secondary sort key is number.
  489.         This final sort sorts the threads into proper chronological
  490.         order, keeping the articles within the threads together. */
  491.         
  492.     return FastQSort(subjectArrayPointers, numSubjects, sizeof(Ptr),
  493.         (short(*)(void*,void*))SortSubjectArrayCompare2);
  494. }
  495.  
  496.  
  497.  
  498. /*----------------------------------------------------------------------------
  499.     OpenGroupCell opens one subject window for a cell in a group 
  500.     list window.
  501.     
  502.     Entry:    wind = pointer to group list window.
  503.             theCell = the cell in the group list window to be opened.
  504.             
  505.     Exit:    function result = 
  506.                 0 if subject window opened.
  507.                 1 if no articles in group or group does not exist.
  508.                 2 if some other error.
  509. ----------------------------------------------------------------------------*/
  510.  
  511. short OpenGroupCell (WindowPtr wind, Cell theCell)
  512. {
  513.     WindowPtr child;
  514.     TWindow **info, **childInfo;
  515.     ListHandle theList, childList;
  516.     TGroup **groupArray, theGroup;
  517.     TSubject **subjectArray=nil;
  518.     TSubject **subjectArrayPointers=nil, **p;
  519.     short numSubjects = 0;
  520.     Handle subjectStrings=nil;
  521.     Handle strings;
  522.     CStr255 groupName;
  523.     short cellDataLen, cellData;
  524.     EWindowKind kind;
  525.     Point firstOffset;
  526.     GrafPtr savePort;
  527.     short *pCells;
  528.     short *pCellArray;
  529.     short offset;
  530.     Cell childCell;
  531.     OSErr err;
  532.     short i;
  533.     short numCells;
  534.     char statusMsg[300];
  535.     long firstFetched, lastFetched;
  536.     TUnread **unread, **nextUnread;
  537.     short result;
  538.     
  539.     /* Check to see if the subject window is already open. Bring it
  540.        to the front if it is. */
  541.  
  542.     if ((child = FindChild(wind, theCell)) != nil) {
  543.         SelectWindow(child);
  544.         return 0;
  545.     }
  546.     
  547.     /* Initialize. */
  548.     
  549.     info = (TWindow**)GetWRefCon(wind);
  550.     kind = (**info).kind;
  551.     theList = (**info).theList;
  552.     groupArray = (**info).groupArray;
  553.     strings = (**info).strings;
  554.     cellDataLen = 2;
  555.     LGetCell(&cellData, &cellDataLen, theCell, theList);
  556.     theGroup = (*groupArray)[cellData];
  557.     strcpy(groupName, *strings + theGroup.nameOffset);
  558.     if (kind == kUserGroup && theGroup.numUnread == 0) goto exit1;
  559.     
  560.     if (kind == kUserGroup) (**info).changed = true;
  561.     
  562.     /* Display the status window. */    
  563.     
  564.     strcpy(statusMsg, gPrefs.showAuthors ?
  565.         "Getting subjects and authors: " :
  566.         "Getting subjects: ");
  567.     strcat(statusMsg, groupName);
  568.     statusMsg[255] = 0;
  569.     StatusWindow(statusMsg);
  570.     
  571.     /* If we are opening a cell in the full group list window or the new groups
  572.        window, first call GetGroupArticleRange to get the [low,high] article range
  573.        for the group. */
  574.  
  575.     if (kind != kUserGroup) {
  576.         result = GetGroupArticleRange(&theGroup);
  577.         if (result == 1) goto exit1;
  578.         if (result == 2) goto exit2;
  579.         if (theGroup.numUnread == 0) goto exit1;
  580.     }
  581.     
  582.     /* Get the subjects and authors from the server. */
  583.     
  584.     if (!GetSubjectsAndAuthorsFromNet(groupName, &theGroup, kind,
  585.         &subjectArray, &numSubjects, &subjectStrings, &firstFetched)) goto exit2;
  586.     if (numSubjects == 0) goto exit1;
  587.     
  588.     /* Apply filters. */
  589.  
  590.     #ifdef FILTERS
  591.         if (kind == kUserGroup) {
  592.             KillArticles(groupName, subjectArray, &numSubjects, subjectStrings);
  593.         }
  594.         if (numSubjects == 0) goto exit1;
  595.     #endif
  596.  
  597.     /* Sort the list into thread order. */
  598.     
  599.     subjectArrayPointers = (TSubject**)MyNewPtr(numSubjects * sizeof(Ptr));
  600.     
  601.     HLock((Handle)subjectArray);
  602.     if (!SortSubjectArray(subjectArray, subjectArrayPointers,
  603.         numSubjects, subjectStrings)) goto exit2;
  604.     HUnlock((Handle)subjectArray);
  605.     
  606.     /* Create the subject window. */
  607.     
  608.     SetPt(&firstOffset, 0, 0);
  609.     GetPort(&savePort);
  610.     SetPort(wind);
  611.     LocalToGlobal(&firstOffset);
  612.     SetPort(savePort);
  613.     
  614.     c2pstr(groupName);
  615.     child = MakeNewWindow(kSubject, firstOffset, (StringPtr)groupName);
  616.     childInfo = (TWindow**)GetWRefCon(child);
  617.     
  618.     (**childInfo).subjectArray = subjectArray;
  619.     (**childInfo).numSubjects = numSubjects;
  620.     (**childInfo).firstFetched = firstFetched;
  621.     (**childInfo).strings = subjectStrings;
  622.     (**childInfo).parentWindow = wind;
  623.     (**childInfo).parentGroup = cellData;
  624.     (**childInfo).groupNameOffset = theGroup.nameOffset;
  625.  
  626.     /* Expand threads with highlighted articles if desired */
  627.  
  628.     #ifdef FILTERS
  629.         if (gPrefs.showThreadsCollapsed && gPrefs.expandHilited) {
  630.             TSubject **threadStart, **q, **end;
  631.             threadStart = subjectArrayPointers;
  632.             end = subjectArrayPointers + numSubjects;
  633.             for (p = subjectArrayPointers; p < end; p++) {
  634.                 if ((**p).threadOrdinal == 1)
  635.                     threadStart = p;
  636.                 if ((**p).collapsed && (**p).highlight) {
  637.                     short thread = (**threadStart).threadHeadIndex;
  638.                     for (q = threadStart; q < end && (**q).threadHeadIndex == thread;
  639.                         q++) {
  640.                         (**q).collapsed = false;
  641.                         p = q;
  642.                     }
  643.                 }
  644.             }
  645.         }
  646.     #endif
  647.  
  648.     /* Create the List Manager cell list. */
  649.     
  650.     childList = (**childInfo).theList;
  651.     p = subjectArrayPointers;
  652.     numCells = numSubjects;
  653.     for (i = 0; i < numSubjects; i++) {
  654.         if ((**p).collapsed && (**p).threadOrdinal > 1) numCells--;
  655.         p++;
  656.     }
  657.     
  658.     LDoDraw(false, childList);
  659.     LAddRow(numCells, 0, childList);
  660.  
  661.     MySetHandleSize((**childList).cells, 2*numCells);
  662.     HLock((**childList).cells);
  663.     HLock((Handle)childList);
  664.     pCells = (short*)*((**childList).cells);
  665.     pCellArray = (**childList).cellArray;
  666.     offset = 0;
  667.  
  668.     p = subjectArrayPointers;
  669.     for (i = 0; i < numSubjects; i++) {
  670.         if (!(**p).collapsed || (**p).threadOrdinal == 1) {
  671.             *pCellArray++ = offset;
  672.             *pCells++ = (**p).myIndex;
  673.             offset += 2;
  674.         }
  675.         p++;
  676.     }
  677.     *pCellArray = offset;
  678.     HUnlock((**childList).cells);
  679.     HUnlock((Handle)childList);
  680.     LDoDraw(true,childList);
  681.     
  682.     MyDisposPtr((Ptr)subjectArrayPointers);
  683.     
  684.     /* Finish initializing the new subject window. */
  685.  
  686.     AddChild(wind, child);
  687.     SetPt(&childCell, 0, 0);
  688.     LSetSelect(true, childCell, childList);
  689.     DoZoom(child, inZoomOut);
  690.     
  691.     /* For a user group list, update the unread list and the number
  692.        of unread articles counter. */
  693.     
  694.     if (kind == kUserGroup) {
  695.         UpdateUnreadList(child);
  696.         (*groupArray)[cellData].onlyRedrawCount = true;
  697.         LDraw(theCell, theList);
  698.         (*groupArray)[cellData].onlyRedrawCount = false;
  699.     }
  700.     
  701.     /* Show the new subject window. */
  702.     
  703.     ShowWindow(child);
  704.         
  705.     return 0;
  706.     
  707. exit1:
  708.  
  709.     MyDisposHandle((Handle)subjectArray);
  710.     MyDisposHandle(subjectStrings);
  711.     MyDisposPtr((Ptr)subjectArrayPointers);
  712.     if (kind == kUserGroup) {
  713.         unread = theGroup.unread;
  714.         while (unread != nil) {
  715.             nextUnread = (**unread).next;
  716.             MyDisposHandle((Handle)unread);
  717.             unread = nextUnread;
  718.         }
  719.         theGroup.unread = nil;
  720.         theGroup.numUnread = 0;
  721.         (*groupArray)[cellData] = theGroup;
  722.         (*groupArray)[cellData].onlyRedrawCount = true;
  723.         LDraw(theCell, theList);
  724.         (*groupArray)[cellData].onlyRedrawCount = false;
  725.     }
  726.     return 1;
  727.     
  728. exit2:
  729.  
  730.     MyDisposHandle((Handle)subjectArray);
  731.     MyDisposHandle(subjectStrings);
  732.     MyDisposPtr((Ptr)subjectArrayPointers);
  733.     return 2;
  734. }
  735.